/* Box blur (simple smooth) problem optimized using CUDA and motion blur. June 2014.
 * Felipe Osorio Thome 7696409 <f.o.thome@gmail.com>, 2014.
 * Thales Andrade 7573527.
 * Vitor Lima 7573361.
 *
 * This work is based on Convolution paper by Nvidia Corporation. */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <cuda_runtime_api.h>
#include <cuda.h>
#include "pbmpak.h"
#include "smooth.h"

#define DEBUG

int main(int argc, char *argv[]) {

  FILE *img = NULL;
  IMG_HEADER header;
  IMG_DATA h_data, h_dataout, d_datain, d_datatemp, d_dataout;
  char filename_in[36], filename_out[36];
  int i, img_size, img_size_aligned, width_aligned;

  /* Initializing structs with "null" data. */
  memset(&header, 0, sizeof(IMG_HEADER));
  memset(&h_data, 0, sizeof(IMG_DATA));

  /* This block is only to simplify the debug. */
  if(argc > 3) {
    strcpy(filename_in, argv[1]);
    strcpy(filename_out, argv[2]);
  }
  else {
    strcpy(filename_in, "in.ppm");
    strcpy(filename_out, "out.ppm");
  }

  img = xfopen(filename_in, "r");

  /* Reading ppm image header. */
  ppma_read_header(img, &(header.xsize), &(header.ysize), &(header.maxrgb));

  /* Calculating the total number of pixels. */
  img_size = header.xsize * header.ysize;

  /* Allocating data input arrays. */
  h_data.rarray = (int *)xcalloc(img_size, sizeof(int));
  h_data.garray = (int *)xcalloc(img_size, sizeof(int));
  h_data.barray = (int *)xcalloc(img_size, sizeof(int));

  /* Reading ppm image data. */
  ppma_read_data(img, header.xsize, header.ysize, h_data.rarray, h_data.garray, h_data.barray);

  /* Allocating and initializing CUDA arrays. */
  fatal( cudaMalloc((void **)&(d_datain.rarray), img_size * sizeof(int)) );
  fatal( cudaMalloc((void **)&(d_datain.garray), img_size * sizeof(int)) );
  fatal( cudaMalloc((void **)&(d_datain.barray), img_size * sizeof(int)) );

  fatal( cudaMalloc((void **)&(d_datatemp.rarray), img_size * sizeof(int)) );
  fatal( cudaMalloc((void **)&(d_datatemp.garray), img_size * sizeof(int)) );
  fatal( cudaMalloc((void **)&(d_datatemp.barray), img_size * sizeof(int)) );

  fatal( cudaMalloc((void **)&(d_dataout.rarray), img_size * sizeof(int)) );
  fatal( cudaMalloc((void **)&(d_dataout.garray), img_size * sizeof(int)) );
  fatal( cudaMalloc((void **)&(d_dataout.barray), img_size * sizeof(int)) );

  fatal( cudaMemcpy(d_datain.rarray, h_data.rarray, img_size * sizeof(int), cudaMemcpyHostToDevice) );
  fatal( cudaMemcpy(d_datain.garray, h_data.garray, img_size * sizeof(int), cudaMemcpyHostToDevice) );
  fatal( cudaMemcpy(d_datain.barray, h_data.barray, img_size * sizeof(int), cudaMemcpyHostToDevice) );

  /* Processing GPU box blur. */
  #ifdef DEBUG
    timeval t1, t2;
    double elapsedTime;
    gettimeofday(&t1, NULL);
  #endif

  convolutionRowsGPU(d_datatemp.rarray, d_datain.rarray, header.xsize, header.ysize);
  convolutionColumnsGPU(d_dataout.rarray, d_datatemp.rarray, header.xsize, header.ysize);

  convolutionRowsGPU(d_datatemp.garray, d_datain.garray, header.xsize, header.ysize);
  convolutionColumnsGPU(d_dataout.garray, d_datatemp.garray, header.xsize, header.ysize);

  convolutionRowsGPU(d_datatemp.barray, d_datain.barray, header.xsize, header.ysize);
  convolutionColumnsGPU(d_dataout.barray, d_datatemp.barray, header.xsize, header.ysize);

  #ifdef DEBUG
    gettimeofday(&t2, NULL);
    elapsedTime = (t2.tv_sec - t1.tv_sec) * 1000.0;
    elapsedTime += (t2.tv_usec - t1.tv_usec) / 1000.0;
    printf("computation time(ms): %.2lf \n", elapsedTime);
  #endif

  /* Getting the results back. */
  fatal( cudaMemcpy(h_data.rarray, d_dataout.rarray, img_size * sizeof(int), cudaMemcpyDeviceToHost) );
  fatal( cudaMemcpy(h_data.garray, d_dataout.garray, img_size * sizeof(int), cudaMemcpyDeviceToHost) );
  fatal( cudaMemcpy(h_data.barray, d_dataout.barray, img_size * sizeof(int), cudaMemcpyDeviceToHost) );

  /* Writing the output file. */
  ppma_write(filename_out, header.xsize, header.ysize, header.maxrgb, h_data.rarray, h_data.garray, h_data.barray);

  /* Freeing memory. */
  fclose(img);

  free(h_data.rarray);
  free(h_data.garray);
  free(h_data.barray);

  fatal( cudaFree(d_datain.rarray) );
  fatal( cudaFree(d_datain.garray) );
  fatal( cudaFree(d_datain.barray) );

  fatal( cudaFree(d_datatemp.rarray) );
  fatal( cudaFree(d_datatemp.garray) );
  fatal( cudaFree(d_datatemp.barray) );

  fatal( cudaFree(d_dataout.rarray) );
  fatal( cudaFree(d_dataout.garray) );
  fatal( cudaFree(d_dataout.barray) );

  return EXIT_SUCCESS;
}

FILE *xfopen(char *filename, char *mode) {
  FILE *ptr = NULL;

  ptr = fopen(filename, mode);

  if(!ptr) {
    fprintf(stderr, "%s: %d: %s\n", __FILE__, __LINE__, strerror(errno));
    exit(EXIT_FAILURE);
  }

  return ptr;
}

void *xcalloc(unsigned long int num, int size) {
  void *ptr = NULL;

  ptr = calloc(num, size);

  if(!ptr) {
    fprintf(stderr, "%s: %d: %s\n", __FILE__, __LINE__, strerror(errno));
    exit(EXIT_FAILURE);
  }

  return ptr;
}
